package _08_blocking_queue.my_implements;

import _00_my.exception.TechnicalLimitationException;
import _04_queue.AbstractQueue;
import _08_blocking_queue.BlockingQueue;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: mornd
 * @dateTime: 2023/6/9 - 16:30
 * 低配版数组实现的阻塞队列
 * 重新入队时会插入队列前面
 * 单锁实现
 */
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E> {
    E[] items;
    /**
     * 大小
     */
    int size;

    /**
     * 记录入队元素的下标
     */
    int putIndex;
    /**
     * 记录出队元素的下标
     */
    int takeIndex;

    /**
     * 锁对象
     */
    final ReentrantLock lock;
    /**
     * 是否已满条件对象
     */
    private final Condition notFull;
    /**
     * 是否是空条件变量
     */
    private final Condition notEmpty;

    @SuppressWarnings("all")
    public ArrayBlockingQueue(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException();
        }
        items = (E[]) new Object[capacity];
        lock = new ReentrantLock(false);
        notFull = lock.newCondition();
        notEmpty = lock.newCondition();
    }

    /**
     * 入队
     *
     * @param e
     */
    private void enqueue(E e) {
        items[putIndex++] = e;
        if (putIndex == items.length) {
            putIndex = 0;
        }
        size++;
        notEmpty.signal();
    }

    /**
     * 入队
     *
     * @param e 如果队列已满就一直阻塞
     * @throws InterruptedException
     */
    @Override
    public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        lock.lockInterruptibly();
        try {
            while (size == items.length) {
                notFull.await();
            }
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 如果队列已满就直接返回 false
     *
     * @param e
     * @return
     */
    @Override
    public boolean offer(E e) {
        Objects.requireNonNull(e);
        lock.lock();
        try {
            if (size == items.length) {
                return false;
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        Objects.requireNonNull(e);
        long nanos = unit.toNanos(timeout);
        lock.lockInterruptibly();
        try {
            while (size == items.length) {
                if(nanos <= 0L) {
                    return false;
                }
                nanos = notFull.awaitNanos(nanos); // 返回值代表剩余时间
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 出队
     *
     * @return
     */
    private E dequeue() {
        E e = items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length) {
            takeIndex = 0;
        }
        size--;
        notFull.signal();
        return e;
    }

    /**
     * 出队
     *
     * @return 如何队列为空则一直阻塞
     * @throws InterruptedException
     */
    @Override
    public E take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (size == 0) {
                notEmpty.await();
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 如果队列为空就直接返回 null
     *
     * @return
     */
    @Override
    public E poll() {
        lock.lock();
        try {
            if (size == 0) {
                return null;
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        lock.lockInterruptibly();
        try {
            while (size == 0) {
                if(nanos <= 0L) {
                    return null;
                }
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public E peek() {
        lock.lock();
        try {
            return items[takeIndex];
        } finally {
            lock.unlock();
        }
    }

    @Override
    public int size() {
        lock.lock();
        try {
            return size;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean isFull() {
        return size() == items.length;
    }

    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    @Override
    public Iterator<E> iterator() {
        throw new TechnicalLimitationException();
    }

    @Override
    public String toString() {
        return Arrays.toString(items);
    }
}
